# --------------------------------------------------------------------------------------------------------------------
# Project:             Digital Payment System (DPS)
# Website:             http://code.google.com/p/dps-x509/
# Purpose of document: This class extracts information out of the Asn1 encoded
#                      x509 file
# --------------------------------------------------------------------------------------------------------------------
import sys, re

sys.path.append("..")

try:
  import simplejson as json
except ImportError:
  import json

#we would prefer the try to pass, but the import stuff isn't working
try:
  import keyczar.keys as keys
  import keyczar.util as util
except ImportError:
  from keyczar import *

import dps.asn1CertificateSpecification
import dps.errors
import dps.utils
import dps.x509AttributeAndValue
import dps.x509CertificateParams
import dps.x509Extension
import dps.x509Helper

# --------------------------------------------------------------------------------------------------------------------
class X509Asn1Extractor:
  """ Responsible for transforming ASN.1 data into python objects 
  
  important conversions to know:
  
  We are dealing with 3 main datatypes:
    - Asn1:   U{http://en.wikipedia.org/wiki/Asn1}
    - Bytes:  U{http://en.wikipedia.org/wiki/Bytes}
    - Base64: U{http://en.wikipedia.org/wiki/Base64}
  """  
  
# --------------------------------------------------------------------------------------------------------------------
  def __init__(self, cert):
    """ open der or base64 certificate string
    @param  cert: must be in der or base64 string formats. cert must be a single item and not a chain
    @type   cert: X509CertificateParams 
    @raise  dps.errors.DecodingError: if unable to create asn1 model from cert
    @rtype: X509Asn1Extractor
    """
    try:
      base64certs = dps.x509Helper.certsToBase64(cert)
      dps.utils.assertion(len(base64certs) == 1, \
                          "X509Asn1Extractor.__init__: extractor only takes one certifcate at a time")
      
      certBytes = dps.x509Helper.base64ToBytes(base64certs[0])
      self.__asn1Cert_ = dps.x509Helper.bytesToAsn1(certBytes, dps.asn1CertificateSpecification.Asn1CertificateSpecification())
        
      dps.utils.out("certificate: "+self.__asn1Cert_.prettyPrint())
      dps.utils.assertion(dps.x509Helper.asn1ToBytes(self.__asn1Cert_) == certBytes, "Reencoding does not match")

      if not self.__isRSA():
        raise dps.errors.PKIMethodNotSupportedError()
      if not self.__isSHA1():
        raise dps.errors.SignatureAlgorithmNotSupportedError()
        
    except dps.errors.DPSBaseError:
      raise     
    except Exception, e:
      dps.utils.out("Exception in X509Asn1Extractor.__init__: " + str(sys.exc_info()[0]) + str(e))
      raise dps.errors.DecodingError()
  
  # --------------------------------------------------------------------------------------------------------------------
  def getSubjectBase64(self):
    """ 
    returns base64 string of subject details 
    @rtype: string 
    """
    ret = ""
    try:
      subjectAsn1 = self.__asn1Cert_.getComponentByName("tbsCertificate").getComponentByName("subject")
      ret = dps.x509Helper.bytesToBase64(dps.x509Helper.asn1ToBytes(subjectAsn1))
    except dps.errors.DPSBaseError:
      raise
    except Exception, e:
      dps.utils.out("Exception in X509Asn1Extractor.getSubjectBase64: " + str(sys.exc_info()[0]) + str(e))
      raise dps.errors.MissingRequiredParametersError("Unable to retrieve issuer information")   
    
    return ret      
    
    # --------------------------------------------------------------------------------------------------------------------
  def getSubject(self):
    """ 
    returns the subject
    @raise  dps.errors.MissingRequiredParametersError: 
    @rtype: x509AttributeAndValueList 
    """
    issuerList = None
    try:
      issuerAsn1 = self.__asn1Cert_.getComponentByName("tbsCertificate").getComponentByName("subject")
      issuerList = dps.x509AttributeAndValue.X509AttributeAndValueList.loadFromAsn1(issuerAsn1)
    except dps.errors.DPSBaseError:
      raise
    except Exception, e:
      dps.utils.out("Exception in X509Asn1Extractor.getSubject: " + str(sys.exc_info()[0]) + str(e))
      raise dps.errors.MissingRequiredParametersError("Unable to retrieve subject information")   
    
    return issuerList

  # --------------------------------------------------------------------------------------------------------------------
  def getIssuerBase64(self):
    """ 
    returns base64 string of issuer details 
    @rtype: string
    """
    ret = ""
    try:
      issuerAsn1 = self.__asn1Cert_.getComponentByName("tbsCertificate").getComponentByName("issuer")
      ret = dps.x509Helper.bytesToBase64(dps.x509Helper.asn1ToBytes(issuerAsn1))
    except dps.errors.DPSBaseError:
      raise
    except Exception, e:
      dps.utils.out("Exception in X509Asn1Extractor.getIssuerBase64: " + str(sys.exc_info()[0]) + str(e))
      raise dps.errors.MissingRequiredParametersError("Unable to retrieve issuer information")   
    
    return ret  
  
  # --------------------------------------------------------------------------------------------------------------------
  def getIssuer(self):
    """ 
    returns the issuer
    @raise dps.errors.MissingRequiredParametersError:
    @rtype: x509AttributeAndValueList 
    """
    issuerList = None
    try:
      issuerAsn1 = self.__asn1Cert_.getComponentByName("tbsCertificate").getComponentByName("issuer")
      issuerList = dps.x509AttributeAndValue.X509AttributeAndValueList.loadFromAsn1(issuerAsn1)
    except dps.errors.DPSBaseError:
      raise
    except Exception, e:
      dps.utils.out("Exception in X509Asn1Extractor.getIssuer: " + str(sys.exc_info()[0]) + str(e))
      raise dps.errors.MissingRequiredParametersError("Unable to retrieve issuer information")   
    
    return issuerList

  # --------------------------------------------------------------------------------------------------------------------
  def getRsaPublicKey(self):
    """ 
    returns public key
    @rtype: keyczar.keys.RsaPublicKey 
    """
    pubRsaKey = None
    try:
      pubAsn1 = self.__asn1Cert_.getComponentByName("tbsCertificate").getComponentByName("subjectPublicKeyInfo")
      pubBase64 = dps.x509Helper.bytesToBase64(dps.x509Helper.asn1ToBytes(pubAsn1))
      
      pubKeyParams = util.ParseX509(pubBase64)
      modBytes = util.TrimBytes(util.BigIntToBytes(pubKeyParams["n"]))
      pubKeyBytes = util.TrimBytes(util.IntToBytes(pubKeyParams["e"]))
      modulusBase64 = dps.x509Helper.bytesToBase64(modBytes)
      pubKeyBase64 = dps.x509Helper.bytesToBase64(pubKeyBytes)
      nbits = len(modBytes) * 8
      
      rsaParams = json.dumps({"publicExponent": pubKeyBase64, "modulus": modulusBase64, "size": nbits})
      dps.utils.out("X509Asn1Extractor.getRsaPublicKey: " + rsaParams)
      pubRsaKey = keys.RsaPublicKey.Read(rsaParams)
      
      dps.utils.assertion(isinstance(pubRsaKey, keys.RsaPublicKey), \
                          "X509Asn1Extractor.getRsaPublicKey: invalid pubRsaKey type")
    except dps.errors.DPSBaseError:
      raise
    except Exception, e:
      dps.utils.out("Exception in X509Asn1Extractor.getRsaPublicKey: " + str(sys.exc_info()[0]) + str(e))
      raise dps.errors.NoRSADataFoundError()
      
    return pubRsaKey
  
  # --------------------------------------------------------------------------------------------------------------------
  def getTbsCertificateBytes(self):
    """ 
    returns only the tbsCertificate portion of the file in bytes 
    @rtype: string
    """
    certBytes = None
    try:
      certBytes = dps.x509Helper.asn1ToBytes(self.__asn1Cert_.getComponentByName("tbsCertificate"))
      dps.utils.assertion(isinstance(certBytes, str), "X509Asn1Extractor.getTbsCertificateBytes: invalid cert type")

    except dps.errors.DPSBaseError:
      raise
    except Exception, e:
      dps.utils.out("Exception in X509Asn1Extractor.getTbsCertificateBytes: " + str(sys.exc_info()[0]) + str(e))
      raise dps.errors.DecodingError()
    
    return certBytes
    
  # --------------------------------------------------------------------------------------------------------------------
  def getSignatureBytes(self):
    """ 
    returns on the signature portion of the file in bytes 
    @rtype: string
    """
    sigBytes = None
    try:
      sigAsn1 = self.__asn1Cert_.getComponentByName("signatureValue")
      sigBinary = ""
      for bit in sigAsn1:
        sigBinary += str(bit)
      sigBytes = util.BinToBytes(sigBinary)
      dps.utils.assertion(isinstance(sigBytes, str), "X509Asn1Extractor.getSignatureBytes: invalid sigBytes type")
    except dps.errors.DPSBaseError:
      raise
    except Exception, e:
      dps.utils.out("Exception in X509Asn1Extractor.getSignatureBytes: " + str(sys.exc_info()[0]) + str(e))
      raise dps.errors.DecodingError()
    
    return sigBytes

  # --------------------------------------------------------------------------------------------------------------------
  def isSelfSigned(self):
    """ 
    if it has an issuer and subject are the same it is not self signed 
    @rtype: string
    """
    isSelf = False
    try:
      issuerAsn = self.__asn1Cert_.getComponentByName("tbsCertificate").getComponentByName("issuer")
      subjectAsn = self.__asn1Cert_.getComponentByName("tbsCertificate").getComponentByName("subject")
      
      issuerBase64 = dps.x509Helper.bytesToBase64(dps.x509Helper.asn1ToBytes(issuerAsn))
      subjectBase64 = dps.x509Helper.bytesToBase64(dps.x509Helper.asn1ToBytes(subjectAsn))
      
      isSelf = issuerBase64 == subjectBase64
    except dps.errors.DPSBaseError:
      pass #raise
    except Exception, e:
      dps.utils.out("Exception in X509Asn1Extractor.isSelfSigned: " + str(sys.exc_info()[0]) + str(e))
      #raise dps.errors.DecodingError()
    dps.utils.out("X509Asn1Extractor.isSelfSigned: " + str(isSelf))
    
    return isSelf   
  
  # --------------------------------------------------------------------------------------------------------------------
  def isCA(self):
    """ 
    is the certificate a CA cert
    @rtype: bool
    """
    return False  
  
  # --------------------------------------------------------------------------------------------------------------------
  def asBase64(self):
    """ 
    base64 representation without header and footer 
    @rtype: string
    """
    return dps.x509Helper.bytesToBase64(dps.x509Helper.asn1ToBytes(self.__asn1Cert_))
  
  # --------------------------------------------------------------------------------------------------------------------
  def __convertExtensionsToParams(self, params):
    """
    extract extensions from certificate 
    @param params: input parameters 
    @type  params: x509CertificateParams 
    """
    try:
      asn1 = self.__asn1Cert_.getComponentByName("tbsCertificate").getComponentByName("extensions")
      extensions = dps.x509Extension.X509ExtensionList.loadFromAsn1(asn1)
      
      tokenTypeOid = dps.x509Extension.stringRepresentationToExtensionOid("tokenType")
      tokenType = extensions[tokenTypeOid]
      if tokenType:
        params.tokenType_ = tokenType.getValue()
      
      tokenQuantityOid = dps.x509Extension.stringRepresentationToExtensionOid("tokenQuantity")
      tokenQuantity = extensions[tokenQuantityOid]
      if tokenQuantity:
        params.tokenQuantity_ = float(tokenQuantity.getValue())        
        
      redeemerOid = dps.x509Extension.stringRepresentationToExtensionOid("redeemer")
      redeemer = extensions[redeemerOid]
      if redeemer:
        params.redeemer_ = redeemer.getValue()
        
      registrarOid = dps.x509Extension.stringRepresentationToExtensionOid("registrar")
      registrar = extensions[registrarOid]
      if registrar:
        params.registrar_ = registrar.getValue()

      exchangerOid = dps.x509Extension.stringRepresentationToExtensionOid("exchanger")
      exchanger = extensions[exchangerOid]
      if exchanger:
        params.exchanger_ = exchanger.getValue()
      
      idOid = dps.x509Extension.stringRepresentationToExtensionOid("id")
      id = extensions[idOid]
      if id:
        params.id_ = id.getValue()
      
      posInChainOid = dps.x509Extension.stringRepresentationToExtensionOid("posInChain")
      posInChain = extensions[posInChainOid]
      if posInChain:
        params.posInChain_ = posInChain.getValue()
    
    except dps.errors.DPSBaseError:
      pass #raise
    except Exception, e:
      dps.utils.out("Exception in X509Asn1Extractor.__convertExtensionsToParams: " + str(sys.exc_info()[0]) + str(e))
    
    return params
  
  
  # --------------------------------------------------------------------------------------------------------------------
  def getParams(self):
    """ 
    extract dps.x509CertificateParams from certificate
    @rtype: x509CertificateParams 
    """
    params = dps.x509CertificateParams.X509CertificateParams()
    params = self.__convertExtensionsToParams(params)
    
    params.setSubject(self.getSubject())
    params.setIssuer(self.getIssuer())
    params.setPublicKey(self.getRsaPublicKey())
    params.isCA_ = self.isCA()
  
    return params
  
  # --------------------------------------------------------------------------------------------------------------------
  def prettyPrint(self):
    """ 
    returns string from asn1 pretty print 
    @rtype: string 
    """
    return self.__asn1Cert_.prettyPrint()

  # --------------------------------------------------------------------------------------------------------------------
  def __isRSA(self):
    """ 
    returns true is certificate if the PKI method is RSA 
    @rtype: bool
    """
    ret = False
    try:
      univAlg = self.__asn1Cert_.getComponentByName("tbsCertificate").getComponentByName("subjectPublicKeyInfo").getComponentByName("algorithm")[0]
      ret = univAlg == util.RSA_OID
      dps.utils.out("X509Asn1Extractor.__isRSA: actual: %s expected: %s result: %s" % (str(univAlg), str(util.RSA_OID), ret))
    except dps.errors.DPSBaseError:
      raise
    except Exception, e:
      dps.utils.out("Exception in X509Asn1Extractor.__isRSA: " + str(sys.exc_info()[0]) + str(e))
      raise dps.errors.MissingRequiredParametersError("Unable to retrieve RSA information")   
    
    return ret      
     
  # --------------------------------------------------------------------------------------------------------------------
  def __isSHA1(self):
    """ 
    returns true is certificate if the hashing method is SHA1 
    @rtype: bool
    """
    ret = False
    try:
      univAlg = self.__asn1Cert_.getComponentByName("tbsCertificate").getComponentByName("signature").getComponentByName("algorithm")
      ret = univAlg == util.SHA1RSA_OID
      dps.utils.out("X509Asn1Extractor.__isRSA: actual: %s expected: %s result: %s" % (str(univAlg), str(util.SHA1RSA_OID), ret))
    except dps.errors.DPSBaseError:
      raise
    except Exception, e:
      dps.utils.out("Exception in X509Asn1Extractor.__isSHA1: " + str(sys.exc_info()[0]) + str(e))
      raise dps.errors.MissingRequiredParametersError("Unable to retrieve SHA1 information")   
    
    return ret    
  
# --------------------------------------------------------------------------------------------------------------------
if __name__ == "__main__":  
  try:    
    base64  = """-----BEGIN CERTIFICATE-----
MIIDnjCCAoagAwIBAgIFAJCZl1EwDQYJKoZIhvcNAQEFBQAwGzEZMBcGA1UEAxMQ
dGVzdEBleGFtcGxlLmNvbTAeFw0xMDA0MjIwNTM5MDRaFw0xMzA0MjIwNTM5MDRa
MBgxFjAUBgNVBAMTDURlYWxlciB0b2tlbnMwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDZoxCo2PV4u0H83SrwgfsQI3d3rFLfqENnX48bswW5JHDj0v3f
xh/Ad122kuxI23cJE9Kc/u6pPFttKB2v+JtqlO1aud2xMF14yWJicde2K8Swb6gG
QC9X2DV53sjehlzlne1Nkfbn+GdHN0LkA943T6D7Q2Pd8AGw6qOV7uHmnZUg1i6D
TB3F5rS7kIitAA+WnYLm/PfnvsCqBakOgy4iLKyRJ6mYDDsjA8dGeHLV5a988dpt
AnlsL1yIz8qg/PQtiJuCzKFXSbVZ8k3w/kdBSl+ElX8Ob2q9HuJBS6cmRXnKRUeX
xiB7iQ5J6jvlVnd9IN1FopnLXUeR/UsUXF8RAgMBAAGjgeswgegwFAYJYIZIAYb4
QgEBAQH/BAQDAgSwMCoGA1UdJQEB/wQgMB4GCCsGAQUFBwMCBggrBgEFBQcDAwYI
KwYBBQUHAwQwDgYDVR0PAQH/BAQDAgO4MA8GA1UdEwEB/wQFMAMBAf8wGQYFVQSn
CAEBAf8EDURlYWxlciB0b2tlbnMwEAYFVQSnCAMBAf8EBC0xLjAwIwYFVQSnCAQB
Af8EF2h0dHA6Ly9zb21lUmVkZWVtZXIuY29tMDEGBVUEpwgGAQH/BCVodHRwOi8v
ZHBzLXg1MDkuYXBwc3BvdC5jb20vcmVnaXN0cmFyMA0GCSqGSIb3DQEBBQUAA4IB
AQAgRHvTPo6YzR/vdiefB0UmYve4ves6d96mIdD7qayRhNkBZeGdIuKdv6Aq7jPn
a9/AV3+sJE8xHa6yqqaWbBKxcbqCQDLu/ClB/L5RGaQPbs7Ko52zwcfpQkLZUC2N
qx0eEhgLGmQjLFoZ2PKUgw5Fj6PocjERmQ6tQ4Uq66RQ8SOy00d+xcelTcNINyI4
63G/HLXdfII7aAvJqG7crfcmt6WfcfLGGUCyW7+CWYkIIeFNA1stpDsr+K5/6Izn
ea3T0TIhrqt7M9NRb/BUL9L74k0Gk8u86xPS29eyyXpWwbfbKVToqu4LzadPuv0/
77XsBaEd8euhy0Gjp7ZTqQrz
-----END CERTIFICATE-----"""
    
    
    c = X509Asn1Extractor(base64)
    print str(c.getParams())
    c.getSignatureBytes()
    c.isSelfSigned()
    print c.getTbsCertificateBytes()
    
  except Exception, e:
    dps.utils.out("An exception occured: " + str(sys.exc_info()[0]) + " " + str(e))

